// // Copyright (c) 2009 All Right Reserved // // vl // // 2009-01-01 // Contains ... namespace LargoCommon.Music { using System; using System.Diagnostics.Contracts; using System.Globalization; using System.Text; using System.Xml.Linq; using System.Xml.Serialization; using Abstract; /// Tone pitch. /// Pitch is an object for keeping given octave and element (formal pitch). /// It allows to determine (real or formal) distances (intervals) to other pitches. /// MidiPitch 0..127, MidiPower 0..127 [Serializable] [XmlRoot] public sealed class MusicalPitch : ICloneable, IComparable { #region Fields /// Inner number of the lowest musical octave. [XmlIgnore] public const int MinOctave = 1; //// -3; /// Inner number of the highest musical octave. [XmlIgnore] public const int MaxOctave = 9; // +5; /// Referential MIDI key number. [XmlIgnore] public const byte BaseKeyNumber = 0; // 48; MIDI /// /// Harmonic system. /// private readonly HarmonicSystem harSystem; #endregion #region Constructors /// Initializes a new instance of the MusicalPitch class. Serializable. public MusicalPitch() { } /// /// Initializes a new instance of the MusicalPitch class. /// /// The given system. public MusicalPitch(HarmonicSystem givenSystem) { Contract.Requires(givenSystem != null); this.harSystem = givenSystem; } /// /// Initializes a new instance of the MusicalPitch class. /// /// The given system. /// Musical octave. /// Element of system. public MusicalPitch(HarmonicSystem givenSystem, short givenOctave, byte element) { Contract.Requires(givenSystem != null); this.harSystem = givenSystem; this.Octave = givenOctave; this.Element = element; this.RecomputeAltitude(); } /// /// Initializes a new instance of the MusicalPitch class. /// /// The given system. /// Pitch altitude. public MusicalPitch(HarmonicSystem givenSystem, int givenAltitude) { Contract.Requires(givenSystem != null); this.harSystem = givenSystem; this.SetAltitude(givenAltitude); } /// Initializes a new instance of the MusicalPitch class. /// Midi Key Number. public MusicalPitch(byte midiKeyNumber) { this.harSystem = HarmonicSystem.GetHarmonicSystem(DefaultValue.HarmonicOrder); this.SetAltitude(midiKeyNumber - BaseKeyNumber); } #endregion #region Properties /// Gets or sets musical octave of the pitch. /// Property description. public short Octave { get; set; } // MinOctave..MaxOctave /// Gets musical octave of the pitch. /// Middle C has StandardOctave = 4 /// Property description. public short StandardOctave => (byte)(this.Octave - 1); /// Gets or sets musical element of the pitch. /// Property description. public byte Element { get; set; } // 0..Order-1 /// Gets or sets musical element of the pitch. /// Property description. public int SystemAltitude { get; set; } // 0..127 /// Gets harmonic system. /// Property description. [XmlIgnore] public HarmonicSystem HarmonicSystem { get { Contract.Ensures(Contract.Result() != null); Contract.Ensures(Contract.Result().Order > 0); if (this.harSystem == null) { throw new InvalidOperationException("Harmonic system is null."); } return this.harSystem; } } /// Gets Xml representation. /// Property description. public XAttribute GetXAttribute => new XAttribute("Pitch", this.SystemAltitude); /// /// Gets MIDI key number. /// /// Property description. /// Returns value. public byte MidiKeyNumber => (byte)(BaseKeyNumber + this.Halftones); /// /// Gets Halftones - MIDI support. /// private int Halftones { get { const float tolerance = 0.001f; var totalHalftones = this.HarmonicSystem.HalftonesForInterval(this.SystemAltitude); var roundHalfTones = (int)Math.Round(totalHalftones); var halftones = Math.Abs(totalHalftones - roundHalfTones) < tolerance ? roundHalfTones : (int)totalHalftones; return halftones; } } #endregion #region Static operators //// TICS rule 7@526: Reference types should not override the equality operator (==) //// public static bool operator ==(MusicalPitch pitch1, MusicalPitch pitch2) { return object.Equals(pitch1, pitch2); } //// public static bool operator !=(MusicalPitch pitch1, MusicalPitch pitch2) { return !object.Equals(pitch1, pitch2); } //// but TICS rule 7@530: Class implements interface 'IComparable' but does not implement '==' and '!='. /// /// Implements the operator <. /// /// The pitch1. /// The pitch2. /// /// Returns value. /// public static bool operator <(MusicalPitch pitch1, MusicalPitch pitch2) { if (pitch1 != null && pitch2 != null) { return pitch1.SystemAltitude < pitch2.SystemAltitude; } return false; } /// /// Implements the operator >. /// /// The pitch1. /// The pitch2. /// /// Returns value. /// public static bool operator >(MusicalPitch pitch1, MusicalPitch pitch2) { if (pitch1 != null && pitch2 != null) { return pitch1.SystemAltitude > pitch2.SystemAltitude; } return false; } /// /// Implements the operator <=. /// /// The pitch1. /// The pitch2. /// /// Returns value. /// public static bool operator <=(MusicalPitch pitch1, MusicalPitch pitch2) { if (pitch1 != null && pitch2 != null) { return pitch1.SystemAltitude <= pitch2.SystemAltitude; } return false; } /// /// Implements the operator >=. /// /// The pitch1. /// The pitch2. /// /// Returns value. /// public static bool operator >=(MusicalPitch pitch1, MusicalPitch pitch2) { if (pitch1 != null && pitch2 != null) { return pitch1.SystemAltitude >= pitch2.SystemAltitude; } return false; } #endregion #region Public static methods /// /// Real Octave Number. /// /// Musical octave. /// Returns value. public static int RealOctaveNumber(int octave) { checked { return octave - 4; } } #endregion #region Public methods /// Makes a deep copy of the MusicalPitch object. /// Returns object. public object Clone() { return new MusicalPitch(this.HarmonicSystem, this.Octave, this.Element); } /// Tests equality with the given pitch. /// Musical pitch. /// Returns value. public bool IsEqualTo(MusicalPitch pitch) { Contract.Requires(pitch != null); if (pitch == null) { return false; } return (this.Octave == pitch.Octave) && (this.Element == pitch.Element); } #endregion #region Comparison /// Comparer of pitches. /// Object to be compared. /// Returns value. public int CompareTo(object obj) { //// This kills the DataGrid //// throw new ArgumentException("Object is not a MusicalPitch"); return obj is MusicalPitch mi ? this.SystemAltitude.CompareTo(mi.SystemAltitude) : 0; } /// Test of equality. /// Object to be compared. /// Returns value. public override bool Equals(object obj) { //// check null (this pointer is never null in C# methods) if (object.ReferenceEquals(obj, null)) { return false; } if (object.ReferenceEquals(this, obj)) { return true; } if (this.GetType() != obj.GetType()) { return false; } return this.CompareTo(obj) == 0; } /// Support of comparison. /// Returns value. public override int GetHashCode() { return this.SystemAltitude.GetHashCode(); } #endregion #region Altitude /// Sets the pitch location. /// Musical octave. /// Element of system. public void SetValues(short octave, byte element) { this.Octave = octave; this.Element = element; this.RecomputeAltitude(); } /// /// Inverts the altitude. /// /// The harmonic order. public void InvertAltitude(byte harmonicOrder) { this.SetAltitude((MaxOctave * harmonicOrder) - this.SystemAltitude); } /// Sets the pitch location number. /// A real system altitude from zero. public void SetAltitude(int givenAltitude) { this.SystemAltitude = givenAltitude; this.Octave = (short)(givenAltitude / this.HarmonicSystem.Order); this.Element = (byte)(givenAltitude % this.HarmonicSystem.Order); } /// Returns the pitch octave location. /// Base of musical pitch measurement. /// Returns value. public float OctaveAltitude(float baseAltitude) { //// return (this.Octave + ((float)this.Element / this.HarmonicSystem.Order)) - baseAltitude; //// Not to be exact, but quick return this.Octave - baseAltitude; } /// /// Inverts the octave. /// public void InvertOctave() { this.Octave = (byte)(MaxOctave - this.Octave); this.RecomputeAltitude(); } /// /// Sets the octave. /// /// The given octave. public void SetOctave(int givenOctave) { this.Octave = (byte)givenOctave; this.RecomputeAltitude(); } /// /// Sets the element. /// /// The given element. public void SetElement(int givenElement) { this.Element = (byte)givenElement; this.RecomputeAltitude(); } /// Shifts octave by given number. /// An octave shift. public void ShiftOctave(int givenShift) { this.Octave = (short)(this.Octave + givenShift); this.RecomputeAltitude(); } /// Shifts element by given number. /// A shift of pitch element. public void ShiftElement(int givenShift) { this.SetAltitude(this.SystemAltitude + givenShift); } /// /// Moves from edges. /// /// The min note. /// The max note. public void MoveFromEdges(byte minNote, byte maxNote) { if (this.SystemAltitude < minNote) { this.ShiftElement(minNote - this.SystemAltitude); } if (this.SystemAltitude > maxNote) { this.ShiftElement(maxNote - this.SystemAltitude); } } #endregion #region Relations /// Compute interval from the given pitch. /// Musical pitch. /// Returns value. public int IntervalFrom(MusicalPitch givenPitch) { if (givenPitch == null) { return 0; } return this.SystemAltitude - givenPitch.SystemAltitude; } /// Compute distance from the given pitch. /// Musical pitch. /// Returns value. public int DistanceFrom(MusicalPitch givenPitch) { var interval = this.IntervalFrom(givenPitch); Contract.Assume(interval > short.MinValue); return Math.Abs(interval); } /// Compute formal distance from the given pitch. /// Musical pitch. /// Returns value. public int FormalDistanceFrom(MusicalPitch givenPitch) { var sysLength = givenPitch?.Element - this.Element ?? 0; var interval = this.HarmonicSystem.FormalMedianLength(sysLength); Contract.Assume(interval > short.MinValue); var formalLength = (byte)Math.Abs(interval); return formalLength; } #endregion #region MIDI support /// Compute MIDI pitch bend for micro intervals. /// Returns value. public byte MidiPitchBend() { byte bend = 64; if (this.HarmonicSystem.Order == DefaultValue.HarmonicOrder) { return bend; } var diff = (float)(this.Halftones - Math.Floor((double)this.Halftones)); var bendDiff = (byte)Math.Floor(64 * diff); bend += bendDiff; return bend; } #endregion #region Serialization /// /// Sets Xml representation. /// /// Xml Element. public void SetXAttribute(XAttribute element) { //// XElement Contract.Requires(element != null); //// if (element == null) { return; } var altitude = (int?)element; ////element.Element("Pitch"); if (altitude != null) { this.SetAltitude((int)altitude); } } #endregion #region String representation /// String representation of the object. /// Returns value. public override string ToString() { var s = new StringBuilder(); //// s.Append(this.Element.ToString("D",System.Globalization.CultureInfo.CurrentCulture.NumberFormat)); var symbol = this.HarmonicSystem.Symbol(this.Element, true); var normalOctave = RealOctaveNumber(this.Octave); if (normalOctave < 0 && normalOctave > short.MinValue && symbol != null) { symbol = symbol.ToUpper(CultureInfo.CurrentCulture); normalOctave = -normalOctave; } s.Append(symbol); s.Append(normalOctave.ToString("D", CultureInfo.CurrentCulture.NumberFormat)); return s.ToString(); } #endregion #region Private methods /// /// Re-computes the altitude. /// private void RecomputeAltitude() { this.SystemAltitude = (this.Octave * this.HarmonicSystem.Order) + this.Element; } #endregion } }